#include <stdexcept>
#include "tdp2_respreload.hpp"
#include "tdp2_allocator.hpp"
#include "tdp2_fpsystem.hpp"
#include "../../rdx/src/rdx/rdx_io.hpp"

using namespace RDX::ObjectManagement;
using namespace RDX::Threading;
using namespace TDP::Threading;

namespace TDP
{
	namespace ResourceManagement
	{
		ResourcePreloader::ResourcePreloader(const PanicHandler *ph, const Allocator &alloc, LargeUInt capacity, LargeUInt alignment, MFPResourceManager *resourceManager)
		{
			_alloc = alloc;
			_alignment = alignment;
			_capacity = capacity;
			_resourceManager = resourceManager;

			memset(&_streamState, 0, sizeof(_streamState));
			_streamState.storage = alloc.reallocFunc(alloc.opaque, NULL, capacity, alignment);
			_streamState.capacity = capacity;
			Threading::NonAtomicWrite(&_streamState.stayAlive, 1);
			_thread = NULL;
			_streamProc.streamState = &_streamState;
			if(!_streamState.storage)
				TDP::Panic(ph, FatalErrors::ERR_AllocationFailed);

			_thread = TDP::Threading::CreateSystemThread(&_streamProc);
		}

		ResourcePreloader::~ResourcePreloader()
		{
			StopStreamingFile();

			// Shut down the IO thread
			if(_thread)
			{
				Threading::AtomicWrite(&_streamState.stayAlive, 0);
				_thread->WaitFor();
				DestroySystemThread(_thread);
				_thread = NULL;
			}

			if(_streamState.storage)
				_alloc.reallocFunc(_alloc.opaque, const_cast<void *>(_streamState.storage), 0, _alignment);
		}

		// Attempts to start streaming a file.  If successful, returns "true"
		bool ResourcePreloader::BeginStreamingFile(const Char *filename, bool sfp, LargeInt offset, LargeInt maxSize)
		{
			StopStreamingFile();

			{
				MutexLock<LightMutex> _(&_streamState.mutex);

				_streamState.pendingStream = _resourceManager->OpenFileRead(filename, sfp);
				_streamState.skip = (offset > 0) ? offset : 0;
				_streamState.maxBytes = maxSize;

				if(_streamState.pendingStream == NULL)
					return false;

				Threading::NonAtomicWrite(&_streamState.stayAlive, 1);
				Threading::NonAtomicWrite(&_streamState.threadActive, 0);
				Threading::NonAtomicWrite(&_streamState.exhausted, 0);
			}

			return true;
		}

		
		bool ResourcePreloader::Read(void *buf, LargeUInt granuleSize, LargeUInt granuleCount, LargeUInt *outReadGranules)
		{
			MutexLock<LightMutex> _(&_streamState.mutex);

			if(_streamState.pendingStream != NULL)	// Thread hasn't picked up the stream yet
			{
				if(outReadGranules != NULL)
					*outReadGranules = 0;
				return true;
			}

			LargeUInt rightHalf, leftHalf;
			LargeUInt filled = _streamState.filledSize;
			const char *bStorage = static_cast<const char *>(const_cast<const void *>(_streamState.storage));

			LargeUInt maxGranules = filled / granuleSize;
			if(maxGranules > granuleCount)
				maxGranules = granuleCount;
			LargeUInt size = maxGranules * granuleSize;

			LargeUInt rightMax = _streamState.capacity - _streamState.start;
			if(rightMax > filled)
			{
				// Right half overflows
				rightHalf = filled;
				leftHalf = 0;
			}
			else
			{
				// Right half does overflow
				rightHalf = rightMax;
				leftHalf = filled - rightMax;
			}
			
			LargeUInt total = rightHalf + leftHalf;

			if(total == 0)
			{
				if(outReadGranules != NULL)
					*outReadGranules = 0;
				return _streamState.exhausted.value == 0;
			}

			if(size == 0 && _streamState.exhausted.value != 0)
			{
				// Remaining data is larger than the requested granule size
				if(outReadGranules != NULL)
					*outReadGranules = 0;
				return false;
			}


			if(size >= rightHalf)
			{
				// Request will exhaust entire right half
				LargeUInt totalRead = rightHalf;
				size -= rightHalf;
				memcpy(buf, bStorage + _streamState.start, rightHalf);
				_streamState.start += rightHalf;
				_streamState.filledSize -= rightHalf;
				buf = static_cast<char *>(buf) + rightHalf;

				if(_streamState.start == _streamState.capacity)
					_streamState.start = 0;

				if(size >= leftHalf)
				{
					// Request will exhaust entire left half
					totalRead += leftHalf;
					memcpy(buf, bStorage, leftHalf);
					_streamState.filledSize = 0;
				}
				else
				{
					// Request will not exhaust entire left half
					totalRead += size;
					memcpy(buf, bStorage, size);
					_streamState.filledSize -= size;
				}
				
				if(outReadGranules != NULL)
					*outReadGranules = totalRead / granuleSize;
				return true;
			}

			// Request will not exhaust the entire right half
			if(outReadGranules != NULL)
				*outReadGranules = maxGranules;
			memcpy(buf, bStorage + _streamState.start, size);
			_streamState.start += size;
			_streamState.filledSize -= size;

			return true;
		}



		// Stops streaming a file.
		void ResourcePreloader::StopStreamingFile()
		{
			MutexLock<LightMutex> _(&_streamState.mutex);
			if(_streamState.stream)
			{
				_streamState.stream->Close();
				_streamState.stream = NULL;
			}
		}

		void ResourcePreloader::AsyncStreamProc::Start() const
		{
			AsyncStreamState *state = this->streamState;

			while(true)
			{
				if(Threading::AtomicRead(&state->stayAlive) != 1)
					return;

				LargeUInt readOffset;
				LargeUInt readSize;

				{
					MutexLock<LightMutex> _(&state->mutex);

					if(state->pendingStream != NULL)
					{
						// Reset, use the new stream
						state->stream = state->pendingStream;
						state->pendingStream = NULL;
						state->start = state->filledSize = 0;
					}

					if (state->stream == NULL)
						readOffset = readSize = 0;
					else if (state->capacity - state->start <= state->filledSize)
					{
						// Upper part is filled, fill from lower
						readOffset = state->start + state->filledSize - state->capacity;;
						readSize = state->start - readOffset;
					}
					else
					{
						// Upper part isn't filled
						readOffset = state->start + state->filledSize;
						readSize = state->capacity - readOffset;
					}
					
					LargeUInt aboveStart = state->capacity - state->start;
				}

				if(readSize == 0)
					SleepThread(10);
				else
				{
					bool clippedRead = false;

					if(state->skip > 0)
					{
						state->stream->Seek(state->skip, RDX::IO::IFileStream::SEEK_Current);
						state->skip = 0;
					}

					if(state->maxBytes > 0)
					{
						if(readSize >= static_cast<LargeUInt>(state->maxBytes))
						{
							readSize = state->maxBytes;
							state->maxBytes = 0;
							clippedRead = true;
						}
						else
							state->maxBytes -= readSize;
					}

					LargeUInt nBytes = static_cast<LargeUInt>(state->stream->ReadBytes(reinterpret_cast<UInt8 *>(const_cast<void *>(state->storage)) + readOffset,
						static_cast<LargeInt>(readSize)));

					MutexLock<LightMutex> _(&state->mutex);

					state->filledSize += nBytes;

					if(nBytes != readSize || clippedRead)
					{
						Threading::NonAtomicWrite(&state->exhausted, 1);
						state->stream = NULL;
					}
				}
			}
		}
	}
}
